/*
 * @Author: 晴鸢. 3013433857@qq.com
 * @Date: 2025-10-12 19:49:49
 * @LastEditors: 晴鸢. 3013433857@qq.com
 * @LastEditTime: 2025-10-15 16:48:43
 * @FilePath: /Learn/udp/UdpServer.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#pragma once
#include<iostream>
#include"log.hpp"
#include<string>
#include<strings.h>
#include<string.h>
#include<unistd.h>
#include<unordered_map>
#include<sys/types.h>
#include<functional>
//套接字相关的接口
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

//相当于一个函数指针
//using func_t=std::function<std::string(const std::string&,const std::string &,uint16_t )>;
Log log;
enum{
    SOCKET_ERR=1,
    BIND_ERR
};

//设置对应的端口值
uint16_t defaultport=8000;
std::string defaultip="0.0.0.0";
const int size=1024;
//一个udp网络服务器
class UdpServer
{
public:
    UdpServer(const uint16_t& port= defaultport,const std::string&ip=defaultip )
        :_port(port)
        ,_ip(ip)
        ,_isRunning(false)
        ,_sockfd(0)
    {}
    //udp服务器的启动代码
    void Init()
    {
        //1.创建upd套接字(socket)
        //2.udp的socket是全双工的，允许被同时读写
        _sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            log(Fatal,"socket create error,sockfd:%d",_sockfd);
            exit(SOCKET_ERR);
        }
        log(Info,"socket create success,socket:%d",_sockfd);
        //2.将套接字绑定端口号(bind socket)
        struct sockaddr_in local;
        bzero(&local,sizeof(local));//将指定的类型大小都清为0
        local.sin_family=AF_INET;//表明对应的结构体类型(协议族)
        local.sin_port=htons(_port);//需要保证我的端口号是网络字节序列，（将主机字节序转化为网络字节序）
        local.sin_addr.s_addr=inet_addr(_ip.c_str());//1.string->uint32_t（转换ip风格）2.uint32_t 必须是网络序列的
    
        //bind内部可以根据sin_family判断local属于哪个协议族
        int n=bind(_sockfd,(const struct sockaddr*)&local,sizeof(local));
        if(n<0)
        {
            log(Fatal,"bind error,error:%d,err string:%s",errno,strerror(errno));
            exit(BIND_ERR);
        }
        log(Info,"bind success,errno:%d,err string:%s",errno,strerror(errno));
    }

    //检查某个用户是否已经存在
    void CheckUser(const struct sockaddr_in&client,const std::string&clientip,uint16_t clientport)
    {
        auto iter=online_user.find(clientip);
        //用户未注册则加入
        if(iter==online_user.end())
        {
            online_user.insert({clientip,client});
             std::cout<<"["<<clientip<<":"<<clientport<<"] add to online user."<<std::endl;
        }
    }

    //向所有的用户发送信息
    void BroadCast(const std::string&str,const std::string&clientip,uint16_t clientport)
    {
        for(const auto &user:online_user)
        {
            std::string message="[";
            message+=clientip;
            message+=":";
            message+=std::to_string(clientport);
            message+="]#";
            message+=str;
            socklen_t len=sizeof(user.second);
            sendto(_sockfd,message.c_str(),message.size(),0,(sockaddr*)&user.second,len);
        }
    }

    void Run()
    {
        //服务器需要持续运行
        _isRunning=true;
        char inbuffer[size];
        while(_isRunning)
        {
            struct sockaddr_in client;//接收消息的对象
            socklen_t len=sizeof(client);
            
            ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(sockaddr*)&client,&len);
            if(n<0)
            {
                log(Warning,"recvfrom error,errno:%d,err string:%s",errno,strerror(errno));
                continue;
            }
            uint16_t clientport=ntohs(client.sin_port);//网络转主机
            std::string clientip=inet_ntoa(client.sin_addr);   
            
            CheckUser(client,clientip,clientport);
            std::string info=inbuffer;
            BroadCast(info,clientip,clientport);
            
        }
    }
    ~UdpServer()
    {
        if(_sockfd>0) close(_sockfd);
    }
private:
    int _sockfd;//网络文件描述符
    std::string _ip;//ip地址（用户通常使用点分十进制字符串风格的IP地址）(120.78.126.148)
    uint16_t _port;//表明服务器进程的端口号
    bool _isRunning;
    std::unordered_map<std::string,struct sockaddr_in> online_user;
};